from __future__ import with_statement
import getopt
import os
import sys
import subprocess
import threading
import Queue
import time

#############################################################################
#
#

def print_string(s):
	print s
	return 0

def print_string_a(s):
	print 'a: %s' % s
	return 0

def print_string_b(s):
	print 'b: %s' % s
	return 0

def print_string_as_execute(s, output_function = None):
	print s
	return 0

#############################################################################
# execute: executes command and calls outerr_filter for every line of
#	output

def execute(command, outerr_filter = None):
	p = subprocess.Popen(command , shell=True, stdin = subprocess.PIPE, \
		stdout = subprocess.PIPE, stderr = subprocess.STDOUT, \
		env = os.environ)
	for line in p.stdout:
		if outerr_filter:
			outerr_filter(line.strip())
	return p.wait()

def __execute2_drain_pipe(queue, pipe):
	for line in pipe:
		queue.put(line.strip())
	return

def execute2(command, out_filter = None, err_filter = None):
	p = subprocess.Popen(command , shell=True, stdin = subprocess.PIPE, \
		stdout = subprocess.PIPE, stderr = subprocess.PIPE, \
		env = os.environ)

	qo = Queue.Queue()
	qe = Queue.Queue()

	to = threading.Thread(target = __execute2_drain_pipe, \
		args = (qo, p.stdout))
	to.start()
	time.sleep(0)
	te = threading.Thread(target = __execute2_drain_pipe, \
		args = (qe, p.stderr))
	te.start()

	while to.isAlive() or te.isAlive():
		time.sleep(1)
		try:
			while True:
				line = qo.get(False)
				if out_filter:
					out_filter(line)
				qo.task_done()
		except Queue.Empty:
			pass

		try:
			while True:
				line = qe.get(False)
				if err_filter:
					err_filter(line)
				qe.task_done()
		except Queue.Empty:
			pass

	to.join()
	te.join()
	return p.wait()

def execute_and_detach(command):
	p = subprocess.Popen( command , shell=True, stdin = subprocess.PIPE, \
		stdout = subprocess.PIPE, stderr = subprocess.STDOUT, \
		env = os.environ)
	return 0

#############################################################################
# mkdir: creates tree of directories

def mkdir(dir_path, is_parents = True, is_verbose = False):
	head, tail = os.path.split(dir_path)
	if head and not os.path.isdir(head):
		assert is_parents, 'Cannot create directory %s' % dir_path
		mkdir(head, is_parents, is_verbose)
	
	if tail:
		if is_verbose:
			print dir_path
		os.mkdir(dir_path)
	return

#############################################################################
# get_file_list_operation: performs operations on lists a and b
#	operation = ('u' | 'd' | 'i')
#	u for union, d for difference, i for intersection

def get_file_list_operation( file_list_a, file_list_b, operation ):
	result_list = []
	my_file_list_b = []

	if os.name == 'posix':
		my_file_list_b = file_list_b
	else:
		for file_b in file_list_b:
			my_file_list_b.append(os.path.abspath(file_b))

	for file_a in file_list_a:
		is_found = False
		for file_b in my_file_list_b:
			if os.name == 'posix':
				if os.path.samefile( file_a, file_b ):
					is_found = True
					break
			else:
				if os.path.abspath(file_a) \
						== file_b: #file_b already abs'ed
					is_found = True
					break
		if operation == 'u':
			result_list.append(file_a)
			if not is_found: result_list.append(file_b)
		elif operation == 'd':
			if not is_found: result_list.append(file_a)
		elif operation == 'i':
			if is_found: result_list.append(file_a)
	return result_list

#############################################################################
# chdir_to_file class: 
#	context manager that changes curdir to specified one

class chdir_to_file( object ):
	def __init__(self, path_to_file ):
		self.__curdir = os.path.realpath( os.curdir )
		self.__dir, self.__file = os.path.split( path_to_file )
		if not self.__dir:
			self.__dir = self.__curdir

	def __enter__(self):
		if self.__dir != self.__curdir:
			os.chdir( self.__dir )
		return self

	def __exit__(self, type, val, tb):
		if self.__dir != self.__curdir:
			os.chdir( self.__curdir )

	def get_dir(self):
		return self.__dir

	def get_file(self):
		return self.__file

#############################################################################
# script class:
#	base class for console scripts

class script(object):
	'''script is a base class for console scripts. It meant to be used this way:
>s = script(sys.argv[1:])
>s.run()

one to override following methods:
	def get_option_tuples(self): must return a list of option tuples
		(option, short, default value, description, is-a-parameter); 
		script class has already some options
	def get_general_description(self): returns string that briefly 
		describes script
	def run(self): place here what script acutally would do'''

	def __init__(self, argv):
		self.__args = []
		self.__option_values = {}

		self.validate_options()
		self.parse_argv(argv)
		return

	def get_args(self):
		return self.__args

	def get_option_value(self, opt):
		try:
			return self.__option_values[opt]
		except:
			return self.get_option_default_value(opt)

	def validate_options(self):
		d = None
		sl = []
		l = self.get_options()
		l.sort()
		r = range(len(l))
		for i in r:
			o = l[i]
			s = self.get_option_short(o)
			if s:
				sl.append(s)
			if ((i + 1) in r) and l[i + 1] == o:
				d = o
				break
		assert not d, 'Duplicate option: %s' % d

		sl.sort()
		r = range(len(sl))
		for i in r:
			s = sl[i]
			if ((i + 1) in r) and sl[i + 1] == s:
				d = s
				break
		assert not d, 'Duplicate short option: %s' % d
		
	def parse_argv(self, argv):
		short = ''
		long = []
		for (o, s, d, e, p) in self.get_option_tuples():
			if p:
				if s:
					s += ':' 
				o += '='
			short += s
			long.append(o)
			
		try:
			opts, args = getopt.getopt(argv, short, long)
		except getopt.GetoptError, err:
			sys.stderr.write('%s\n' % str(err))
			sys.exit(2)

		self.__args = args

		for opt, arg in opts:
			is_found = False
			for (o, s, d, e, p) in self.get_option_tuples():
				t = ('--%s' % o,)
				if s:
					t = ('-%s' % s, '--%s' % o)
				if opt in t:
					is_found = True
					if p:
						self.__option_values[o] = arg
					else:
						self.__option_values[o] = True
			assert is_found, 'Unhandled option: %s' % opt

	def print_usage(self):
		print self.get_general_description()
		print self.get_options_description()

	def get_options_description(self):
		r = 'Options:'
		for (o, s, d, e, p) in self.get_option_tuples():
			r += '\n\t'
			if s:
				r += '-%s,' % s
			else:
				r += '   '
			r += ' --%s' % o
			if len(o) < 10:
				r += '\t'
			r += '\t%s' % e
			if d:
				r += '; Default is "%s"' % d
		return r

	def run(self):
		if self.get_option_value('help'):
			self.print_usage()
			sys.exit()

	def get_option_tuples(self):
		l = [
			('help', 'h', False, 'Print this', False),
			('verbose', 'v', False, 'Be verbose', False)
		]
		return l

	def get_options(self):
		l = []
		for (o, s, d, e, p) in self.get_option_tuples():
			l.append(o)
		return l

	def get_option_short(self, opt):
		(o, s, d, e, p) = self.get_option_tuple(opt)
		return s

	def get_option_description(self, opt):
		(o, s, d, e, p) = self.get_option_tuple(opt)
		return e

	def get_option_has_parameter(self, opt):
		(o, s, d, e, p) = self.get_option_tuple(opt)
		return p

	def get_option_default_value(self, opt):
		(o, s, d, e, p) = self.get_option_tuple(opt)
		return d

	def get_option_tuple(self, opt):
		for t in self.get_option_tuples():
			if t[0] == opt:
				return t
		assert False, 'Option "%s" not defined' % opt
		return None

	def get_general_description(self):
		s = \
'''
Usage:
	<script>.py [options] arguments [...]
Do something with arguments with respect to options.
'''
		return s

	def execute_out_filter(self, line):
		return line

	def execute_err_filter(self, line):
		return line

	def execute(self, command):
		return execute2(command, self.__execute_out, self.__execute_err)	
	
	def __execute_out(self, line):
		line = self.execute_out_filter(line.strip())
		if line:
			sys.stdout.write(line + '\n')
		return

	def __execute_err(self, line):
		line = self.execute_err_filter(line.strip())
		if line:
			sys.stderr.write(line + '\n')
		return
	
if __name__ == '__main__':
	print 'execute() test'
	with chdir_to_file('c:\\work\\any') as c:
		print os.path.abspath(os.curdir)
		execute2('cvs update Documents', print_string_a, print_string_b)

	print 'chdir_to_file test'
	for s in sys.argv[1:]:
		with chdir_to_file(s) as c:
			print 'Current directory is %s' % os.path.realpath( os.curdir )

